package cn.thens.kdroid.core.io

import android.net.Uri
import android.util.Base64
import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.charset.Charset

@Suppress("unused")
object StringCodec {
    fun toBoolean() = TO_BOOLEAN

    private val TO_BOOLEAN = Codec.create(String::toBoolean, Boolean::toString)

    fun toShort() = TO_SHORT

    private val TO_SHORT = Codec.create(String::toShort, Short::toString)

    fun toInt() = TO_INT

    private val TO_INT = Codec.create(String::toInt, Int::toString)

    fun toLong() = TO_LONG

    private val TO_LONG = Codec.create(String::toLong, Long::toString)

    fun toFloat() = TO_FLOAT

    private val TO_FLOAT = Codec.create(String::toFloat, Float::toString)

    fun toDouble() = TO_DOUBLE

    private val TO_DOUBLE = Codec.create(String::toDouble, Double::toString)

    fun toCharArray() = TO_CHAR_ARRAY

    private val TO_CHAR_ARRAY = Codec.create(String::toCharArray, CharArray::toString)

    fun toByteArray(charset: Charset = Charsets.UTF_8): Codec<String, ByteArray> {
        return object : Codec<String, ByteArray> {
            override fun decode(cipher: ByteArray): String {
                return String(cipher, charset)
            }

            override fun encode(plain: String): ByteArray {
                return plain.toByteArray(charset)
            }
        }
    }

    inline fun <reified T> json(json: Json = Json): Codec<String, T> {
        return json.toCodec()
    }


    fun toHex() = TO_HEX

    private val TO_HEX = object : Codec<String, ByteArray> {
        @Suppress("SpellCheckingInspection")
        val alphabet = "0123456789ABCDEF"

        override fun decode(cipher: ByteArray): String {
            val buffer = StringBuilder()
            for (b in cipher) {
                var hex = Integer.toHexString(b.toInt() and 0xFF)
                if (hex.length == 1) hex = "0$hex"
                buffer.append(hex)
            }
            return buffer.toString()
        }

        override fun encode(plain: String): ByteArray {
            if (plain.isEmpty()) return byteArrayOf()
            val value = plain.toUpperCase()
            val hexCharArray = value.toCharArray()
            val halfLength = value.length / 2
            val result = ByteArray(halfLength)
            for (i in 0 until halfLength) {
                val index = i * 2
                val high = alphabet.indexOf(hexCharArray[index])
                val low = alphabet.indexOf(hexCharArray[index + 1])
                result[i] = ((high shl 4) or low).toByte()
            }
            return result
        }
    }

    fun toBase16(): Codec<String, ByteArray> = toHex()

    fun toBase64(flags: Int = Base64.NO_WRAP): Codec<String, ByteArray> {
        return toByteArray()
                .then(object : Codec<ByteArray, ByteArray> {
                    override fun encode(plain: ByteArray): ByteArray {
                        return Base64.encode(plain, flags)
                    }

                    override fun decode(cipher: ByteArray): ByteArray {
                        return Base64.decode(cipher, flags);
                    }
                })
    }

    /**
     * decodes characters except `[0-9A-Za-z_\-\.\*~\(\)\!']` as '%'-escaped octets using the UTF-8 scheme.
     */
    fun toUri() = TO_URI

    private val TO_URI = Codec.create(Uri::encode, Uri::decode)

    /**
     * decodes characters except `[^0-9A-Za-z_\-\.\*]` as '%'-escaped octets
     * For example: '#' -> %23. In addition, spaces are substituted by '+'.
     */
    fun toUrl(charset: Charset = Charsets.UTF_8): Codec<String, String> {
        val charsetName = charset.name()
        return object : Codec<String, String> {
            override fun decode(cipher: String): String {
                return URLDecoder.decode(cipher, charsetName)
            }

            override fun encode(plain: String): String {
                return URLEncoder.encode(plain, charsetName)
            }
        }
    }

    fun encryptToHex(crypto: Crypto): Codec<String, String> {
        return toByteArray()
                .then(crypto.toCodec())
                .then(toHex().inverted())
    }
}